package org.az.netty_study.p06_netty组件之channel;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.Scanner;

/**
 * 方法:
 * 1.close():关闭channel
 * 2.closeFuture():处理channel的关闭
 * - sync():同步等待channel的关闭
 * - addListener():异步等待channel关闭
 * 3.pipeline():添加handler
 * 4.write():将数据写入channel(只是写入了缓冲区，不会立即发送)
 * 5.flush():立即发送缓存区的数据
 * 6.writeAndFlush():将数据写入并立即发送
 */
@Slf4j
public class _1_channel常用方法 {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap client = new Bootstrap()
                .group(group)
                .channel(NioSocketChannel.class)
                .handler(
                        new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
                                //打印日志的handler
                                ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                                //StringEncoder同样是netty提供的，将发送的字符串转为字节写入channel
                                ch.pipeline().addLast(new StringEncoder());
                            }
                        }
                );
        /**
         * Future是juc的那个Future，表示未来，ChannelFuture就是未来的channel(本质是其内部持有了一个channel)
         * 客户端发起连接后，就会返回一个channel，只不过这个channel是ChannelFuture，因为服务端要经过三次握手后才会建立与客户端的连接，可能此时连接还没有真正建立
         * 所以ChannelFuture不能马上写入和读取数据，需要等待与服务端在未来真正建立连接后，这个ChannelFuture才能被视为前面的那种channel(这就是"未来的channel"的含义)
         * ChannelFuture.sync()是一个阻塞方法，表示连接真正建立后，才会往下面运行，在此之前会一直阻塞
         * ChannelFuture.connect表示获取连接.是个异步非阻塞方法
         *  还记得什么是异步吗?
         *  复习：同步 -> 发起操作的线程自己等待和获取结果
         *       异步 -> 发起操作的线程自己不关心结果，而是发起一个操作后就去干其他事，让其他线程去等待和获取结果
         *  ChannelFuture.connect方法表示主线程发起连接操作后，不关心结果，立即往下运行，可能很久以后，连接才建立好
         * 以后发现Future、Promise的，都是跟异步相关的
         */
        ChannelFuture connect = client.connect(new InetSocketAddress("localhost", 9001));
        //获取ChannelFuture持有的channel，发现没有打印ip和端口信息，说明channel没有建立连接
        //log.info("{}", connect.channel());

        /**
         * 怎么同步获取连接的结果呢?
         *  1.sync:同步方法,发起sync操作的线程苦苦等待连接的建立
         *  2.addListener:异步方法,发起操作的线程不等待连接的建立，而是将来连接建立后，让另外的线程回调这里传入的方法
         * 上面两个方法二选一
         */
//        connect = connect.sync();
//        //运行到这，说明连接建立了，就可以往channel里发送数据啦!
//        connect.channel().writeAndFlush("hello server");

        connect.addListener((ChannelFutureListener) channelFuture -> {
            //连接建立后，nio线程就会调用这个回调函数
            log.info("{} - {}", "连接建立啦!", channelFuture.channel());
            channelFuture.channel().writeAndFlush("hello server");
        });

        /**
         * 客户端正确的关闭
         * channel关闭后，还希望做一些善后的工作
         * channel.close()是一个异步操作，交给其他线程来执行关闭操作，不确定什么时候channel真正关闭
         * 所以需要配合其他方法来使用,channel.closeFuture()有一个返回值ChannelFuture
         *  1.ChannelFuture.sync方法(阻塞)，只有channel真正关闭后，该方法才会往下继续运行
         *  2.ChannelFuture.addListener，异步方法,将来channel连接真正关闭后后，让另外的线程回调这里传入的方法
         */
        new Thread(() -> {
            Channel channel = connect.channel();
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String str = scanner.nextLine();
                if ("q".equals(str)) {
                    channel.close();
                    break;
                }
                channel.writeAndFlush(str);
            }
        }, "sendThread").start();

        //优雅关闭1:同步关闭
        ChannelFuture closeFuture = connect.channel().closeFuture();
        //又是一个同步阻塞方法，只有将来调用channel.close()后，channel真正关闭时才会继续往下运行
        log.info("等待关闭");
        closeFuture.sync();
        log.info("善后操作...");

        //优雅关闭2:异步关闭
//        closeFuture.addListener((ChannelFutureListener) channelFuture -> {
//            //将来channel真正关闭后，才会回调这个方法
//            log.info("善后操作...");
//        });

        /**
         * 关闭后，为什么客户端还在运行呢?
         *  - 因为NioEventLoopGroup里还有一些线程在运行
         * 怎么优雅关闭这些线程呢?
         *  - NioEventLoopGroup提供了关闭的方法
         */
        group.shutdownGracefully();

        /**
         * 通过上面的方法，我们发现netty中很多方法都是异步的，也就是一个线程发起操作后，由其他线程去真正执行操作和等待结果
         */
    }
}
